const float BALLOON_RADIUS = 55.0f / 2.0f;
const float BASKET_RADIUS = 15 / 2;

class Balloon {

  vec4 centre_;
  vec4 velocity_;
  vec4 forces_;
  vec4 influence_;
  vec4 end_;

  Sprite balloonSprite_;
  Sprite basketSprite_;

  int leftBound_;
  int rightBound_;
  int topBound_;
  int bottomBound_;

  float mass_;
  float gravity_;
  float lift_;

  float length_;

  float angle_;

  // ----- Basket fields
  vec4 basketCentre_;
  float basketMass_;
  vec4 basketVelocity_;
  vec4 basketTensionVec_;
  float basketTension_;
  float basketDistance_;
  float basketAngle_;
  vec4 basketForces_;

  bool basketAttached_;  
  bool tooLow_;

  void initialiseBasket() {
    basketSprite_ = Sprite(TX_BASKET, vec4(BASKET_RADIUS, BASKET_RADIUS, 0, 0));
    basketDistance_ = length_ = 0 - BALLOON_RADIUS - 4.6f;
    basketCentre_ = vec4(centre_[0], centre_[1] - basketDistance_, 0, 1);
    basketVelocity_ = vec4(0, 0, 0, 0);

    basketMass_ = 5000;
    basketAttached_ = true;

    basketTension_ = basketMass_ * gravity_;
  }

  void drawBasket(shader &shader, texture* tex, mat4 &modelToProjection) {
    basketSprite_.draw(basketCentre_, true, angle_, shader, tex, modelToProjection);
  }
  
  void updateBasket() {
    vec4 acceleration;

    if (basketAttached_) {
      basketTensionVec_[0] = basketTension_ * sin(angle_);
      basketTensionVec_[1] = basketTension_ * cos(angle_);
    } else {
      basketTensionVec_[0] = 0;
      basketTensionVec_[1] = 0;
    }

    basketForces_[0] = basketTensionVec_[0];
    if (basketVelocity_[0] > 0) {
      basketForces_[0] += 0 - resistanceAgainstMotion(basketVelocity_[0]);
    } else if (basketVelocity_[0] < 0) {
      basketForces_[0] += resistanceAgainstMotion(basketVelocity_[0]);
    }
    acceleration[0] = basketForces_[0] / basketMass_;

    basketForces_[1] = basketTensionVec_[1] - (basketMass_ * gravity_);
    if (basketVelocity_[1] > 0) {
      basketForces_[1] += 0 - resistanceAgainstMotion(basketVelocity_[1]);
    } else if (basketVelocity_[1] < 0) {
      basketForces_[1] += resistanceAgainstMotion(basketVelocity_[1]);
    }
    acceleration[1] = basketForces_[1] / basketMass_;

    basketVelocity_ = basketVelocity_ + acceleration;
    basketCentre_ = basketCentre_ + basketVelocity_;

    if (centre_[1] + BALLOON_RADIUS < bottomBound_) {
      tooLow_ = true;
    }
  }

public:
  Balloon() {
    balloonSprite_ = Sprite(TX_RED_BALLOON, vec4(BALLOON_RADIUS, BALLOON_RADIUS, 0, 0));
    reset();
    initialiseBasket();
  }

  void reset() {
    centre_ = vec4(0, -240 + 55, 0, 1);

    velocity_ = vec4(0, 15, 0, 0);
    influence_ = vec4(0, 0, 0, 0);

    mass_ = 0.047f;
    gravity_ = gravity(30);
    lift_ = 0.015f;

    end_ = vec4(0, 0, 0, 1);

    angle_ = 0;

    tooLow_ = false;
  }


  void setVelocity(vec4 v) {
    velocity_ = v;
  }


  void setPosition(vec4 pos) {
    centre_ = pos;
  }

  float getX() {return centre_[0];}
  float getY() {return centre_[1];}

  void influence(float x, float y) {
    influence_[0] = x;
    influence_[1] = y;
  }

  bool isTooLow() {return tooLow_;}

  void update() {

    updateBasket();

    vec4 acceleration;

    forces_[0] = influence_[0];

    if(velocity_[0] > 0) {
      forces_[0] += 0 - resistanceAgainstMotion(velocity_[0]);
    } else if (velocity_[0] < 0) {
      forces_[0] += resistanceAgainstMotion(velocity_[0]);
    }

    acceleration[0] = forces_[0] / mass_;

    forces_[1] = influence_[1] - (mass_ * gravity_) + lift_; // + tensionY;

    if (velocity_[1] > 0) {
      forces_[1] += 0 - resistanceAgainstMotion(velocity_[1]);
    } else if (velocity_[1] < 0) {
      forces_[1] += resistanceAgainstMotion(velocity_[1]);
    }

    acceleration[1] = forces_[1] / mass_;

    velocity_ = velocity_ + acceleration;
    centre_ = centre_ + velocity_;

    if (centre_[0] + BALLOON_RADIUS > rightBound_) {
      centre_[0] = rightBound_ - BALLOON_RADIUS;
      velocity_[0] = -velocity_[0];
    }

    if (centre_[0] - BALLOON_RADIUS < leftBound_) {
      centre_[0] = leftBound_ + BALLOON_RADIUS;
      velocity_[0] = -velocity_[0];
    }

    if (centre_[1] + BALLOON_RADIUS < bottomBound_) {
      tooLow_ = true;
    }

    if (basketAttached_) {
      vec4 diff = basketCentre_ - centre_;
        
      if (diff[1] == 0) {
      } else {
        angle_ = atan(diff[0] / diff[1]);
      }

      end_[0] = centre_[0] + length_ * sin(angle_);
      end_[1] = centre_[1] + length_ * cos(angle_);
      
      basketCentre_ = end_;
    }
    
    influence_[0] = 0;
    influence_[1] = 0;
  }

  bool isAttached() {return basketAttached_;}

  void draw(shader &shader, texture* tex, mat4 &modelToProjection) {
    balloonSprite_.draw(centre_, true, shader, tex, modelToProjection);
    basketSprite_.draw(basketCentre_, true, angle_, shader, tex, modelToProjection);
  }

  void setBounds(int left, int right, int top, int bottom) {
    leftBound_ = left;
    rightBound_ = right;
    topBound_ = top;
    bottomBound_ = bottom;
  }

  ~Balloon() {}
};
